1   /*
2    * Copyright (C) 2008 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.base;
18  
19  import com.google.common.annotations.GwtCompatible;
20  import com.google.common.base.Joiner.MapJoiner;
21  import com.google.common.collect.ImmutableMap;
22  import com.google.common.collect.ImmutableMultimap;
23  import com.google.common.collect.ImmutableSet;
24  import com.google.common.collect.Lists;
25  import com.google.common.collect.Maps;
26  
27  import junit.framework.AssertionFailedError;
28  import junit.framework.TestCase;
29  
30  import java.io.IOException;
31  import java.util.Arrays;
32  import java.util.Iterator;
33  import java.util.Map;
34  import java.util.Set;
35  
36  /**
37   * Unit test for {@link Joiner}.
38   *
39   * @author Kevin Bourrillion
40   */
41  @GwtCompatible(emulated = true)
42  public class JoinerTest extends TestCase {
43    private static final Joiner J = Joiner.on("-");
44  
45    // <Integer> needed to prevent warning :(
46    private static final Iterable<Integer> ITERABLE_ = Arrays.<Integer>asList();
47    private static final Iterable<Integer> ITERABLE_1 = Arrays.asList(1);
48    private static final Iterable<Integer> ITERABLE_12 = Arrays.asList(1, 2);
49    private static final Iterable<Integer> ITERABLE_123 = Arrays.asList(1, 2, 3);
50    private static final Iterable<Integer> ITERABLE_NULL = Arrays.asList((Integer) null);
51    private static final Iterable<Integer> ITERABLE_NULL_NULL
52        = Arrays.asList((Integer) null, null);
53    private static final Iterable<Integer> ITERABLE_NULL_1 = Arrays.asList(null, 1);
54    private static final Iterable<Integer> ITERABLE_1_NULL = Arrays.asList(1, null);
55    private static final Iterable<Integer> ITERABLE_1_NULL_2 = Arrays.asList(1, null, 2);
56    private static final Iterable<Integer> ITERABLE_FOUR_NULLS
57        = Arrays.asList((Integer) null, null, null, null);
58  
59    public void testNoSpecialNullBehavior() {
60      checkNoOutput(J, ITERABLE_);
61      checkResult(J, ITERABLE_1, "1");
62      checkResult(J, ITERABLE_12, "1-2");
63      checkResult(J, ITERABLE_123, "1-2-3");
64  
65      try {
66        J.join(ITERABLE_NULL);
67        fail();
68      } catch (NullPointerException expected) {
69      }
70      try {
71        J.join(ITERABLE_1_NULL_2);
72        fail();
73      } catch (NullPointerException expected) {
74      }
75  
76      try {
77        J.join(ITERABLE_NULL.iterator());
78        fail();
79      } catch (NullPointerException expected) {
80      }
81      try {
82        J.join(ITERABLE_1_NULL_2.iterator());
83        fail();
84      } catch (NullPointerException expected) {
85      }
86    }
87  
88    public void testOnCharOverride() {
89      Joiner onChar = Joiner.on('-');
90      checkNoOutput(onChar, ITERABLE_);
91      checkResult(onChar, ITERABLE_1, "1");
92      checkResult(onChar, ITERABLE_12, "1-2");
93      checkResult(onChar, ITERABLE_123, "1-2-3");
94    }
95  
96    public void testSkipNulls() {
97      Joiner skipNulls = J.skipNulls();
98      checkNoOutput(skipNulls, ITERABLE_);
99      checkNoOutput(skipNulls, ITERABLE_NULL);
100     checkNoOutput(skipNulls, ITERABLE_NULL_NULL);
101     checkNoOutput(skipNulls, ITERABLE_FOUR_NULLS);
102     checkResult(skipNulls, ITERABLE_1, "1");
103     checkResult(skipNulls, ITERABLE_12, "1-2");
104     checkResult(skipNulls, ITERABLE_123, "1-2-3");
105     checkResult(skipNulls, ITERABLE_NULL_1, "1");
106     checkResult(skipNulls, ITERABLE_1_NULL, "1");
107     checkResult(skipNulls, ITERABLE_1_NULL_2, "1-2");
108   }
109 
110   public void testUseForNull() {
111     Joiner zeroForNull = J.useForNull("0");
112     checkNoOutput(zeroForNull, ITERABLE_);
113     checkResult(zeroForNull, ITERABLE_1, "1");
114     checkResult(zeroForNull, ITERABLE_12, "1-2");
115     checkResult(zeroForNull, ITERABLE_123, "1-2-3");
116     checkResult(zeroForNull, ITERABLE_NULL, "0");
117     checkResult(zeroForNull, ITERABLE_NULL_NULL, "0-0");
118     checkResult(zeroForNull, ITERABLE_NULL_1, "0-1");
119     checkResult(zeroForNull, ITERABLE_1_NULL, "1-0");
120     checkResult(zeroForNull, ITERABLE_1_NULL_2, "1-0-2");
121     checkResult(zeroForNull, ITERABLE_FOUR_NULLS, "0-0-0-0");
122   }
123 
124   private static void checkNoOutput(Joiner joiner, Iterable<Integer> set) {
125     assertEquals("", joiner.join(set));
126     assertEquals("", joiner.join(set.iterator()));
127 
128     Object[] array = Lists.newArrayList(set).toArray(new Integer[0]);
129     assertEquals("", joiner.join(array));
130 
131     StringBuilder sb1FromIterable = new StringBuilder();
132     assertSame(sb1FromIterable, joiner.appendTo(sb1FromIterable, set));
133     assertEquals(0, sb1FromIterable.length());
134 
135     StringBuilder sb1FromIterator = new StringBuilder();
136     assertSame(sb1FromIterator, joiner.appendTo(sb1FromIterator, set));
137     assertEquals(0, sb1FromIterator.length());
138 
139     StringBuilder sb2 = new StringBuilder();
140     assertSame(sb2, joiner.appendTo(sb2, array));
141     assertEquals(0, sb2.length());
142 
143     try {
144       joiner.appendTo(NASTY_APPENDABLE, set);
145     } catch (IOException e) {
146       throw new AssertionError(e);
147     }
148 
149     try {
150       joiner.appendTo(NASTY_APPENDABLE, set.iterator());
151     } catch (IOException e) {
152       throw new AssertionError(e);
153     }
154 
155     try {
156       joiner.appendTo(NASTY_APPENDABLE, array);
157     } catch (IOException e) {
158       throw new AssertionError(e);
159     }
160   }
161 
162   private static final Appendable NASTY_APPENDABLE = new Appendable() {
163     @Override
164     public Appendable append(CharSequence csq) throws IOException {
165       throw new IOException();
166     }
167     @Override
168     public Appendable append(CharSequence csq, int start, int end) throws IOException {
169       throw new IOException();
170     }
171     @Override
172     public Appendable append(char c) throws IOException {
173       throw new IOException();
174     }
175   };
176 
177   private static void checkResult(Joiner joiner, Iterable<Integer> parts, String expected) {
178     assertEquals(expected, joiner.join(parts));
179     assertEquals(expected, joiner.join(parts.iterator()));
180 
181     StringBuilder sb1FromIterable = new StringBuilder().append('x');
182     joiner.appendTo(sb1FromIterable, parts);
183     assertEquals("x" + expected, sb1FromIterable.toString());
184 
185     StringBuilder sb1FromIterator = new StringBuilder().append('x');
186     joiner.appendTo(sb1FromIterator, parts.iterator());
187     assertEquals("x" + expected, sb1FromIterator.toString());
188 
189     Integer[] partsArray = Lists.newArrayList(parts).toArray(new Integer[0]);
190     assertEquals(expected, joiner.join(partsArray));
191 
192     StringBuilder sb2 = new StringBuilder().append('x');
193     joiner.appendTo(sb2, partsArray);
194     assertEquals("x" + expected, sb2.toString());
195 
196     int num = partsArray.length - 2;
197     if (num >= 0) {
198       Object[] rest = new Integer[num];
199       for (int i = 0; i < num; i++) {
200         rest[i] = partsArray[i + 2];
201       }
202 
203       assertEquals(expected, joiner.join(partsArray[0], partsArray[1], rest));
204 
205       StringBuilder sb3 = new StringBuilder().append('x');
206       joiner.appendTo(sb3, partsArray[0], partsArray[1], rest);
207       assertEquals("x" + expected, sb3.toString());
208     }
209   }
210 
211   public void test_useForNull_skipNulls() {
212     Joiner j = Joiner.on("x").useForNull("y");
213     try {
214       j = j.skipNulls();
215       fail();
216     } catch (UnsupportedOperationException expected) {
217     }
218   }
219 
220   public void test_skipNulls_useForNull() {
221     Joiner j = Joiner.on("x").skipNulls();
222     try {
223       j = j.useForNull("y");
224       fail();
225     } catch (UnsupportedOperationException expected) {
226     }
227   }
228 
229   public void test_useForNull_twice() {
230     Joiner j = Joiner.on("x").useForNull("y");
231     try {
232       j = j.useForNull("y");
233       fail();
234     } catch (UnsupportedOperationException expected) {
235     }
236   }
237 
238   public void testMap() {
239     MapJoiner j = Joiner.on(";").withKeyValueSeparator(":");
240     assertEquals("", j.join(ImmutableMap.of()));
241     assertEquals(":", j.join(ImmutableMap.of("", "")));
242 
243     Map<String, String> mapWithNulls = Maps.newLinkedHashMap();
244     mapWithNulls.put("a", null);
245     mapWithNulls.put(null, "b");
246 
247     try {
248       j.join(mapWithNulls);
249       fail();
250     } catch (NullPointerException expected) {
251     }
252 
253     assertEquals("a:00;00:b", j.useForNull("00").join(mapWithNulls));
254 
255     StringBuilder sb = new StringBuilder();
256     j.appendTo(sb, ImmutableMap.of(1, 2, 3, 4, 5, 6));
257     assertEquals("1:2;3:4;5:6", sb.toString());
258   }
259 
260   public void testEntries() {
261     MapJoiner j = Joiner.on(";").withKeyValueSeparator(":");
262     assertEquals("", j.join(ImmutableMultimap.of().entries()));
263     assertEquals("", j.join(ImmutableMultimap.of().entries().iterator()));
264     assertEquals(":", j.join(ImmutableMultimap.of("", "").entries()));
265     assertEquals(":", j.join(ImmutableMultimap.of("", "").entries().iterator()));
266     assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries()));
267     assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries().iterator()));
268 
269     Map<String, String> mapWithNulls = Maps.newLinkedHashMap();
270     mapWithNulls.put("a", null);
271     mapWithNulls.put(null, "b");
272     Set<Map.Entry<String, String>> entriesWithNulls = mapWithNulls.entrySet();
273 
274     try {
275       j.join(entriesWithNulls);
276       fail();
277     } catch (NullPointerException expected) {
278     }
279 
280     try {
281       j.join(entriesWithNulls.iterator());
282       fail();
283     } catch (NullPointerException expected) {
284     }
285 
286     assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls));
287     assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls.iterator()));
288 
289     StringBuilder sb1 = new StringBuilder();
290     j.appendTo(sb1, ImmutableMultimap.of(1, 2, 3, 4, 5, 6, 1, 3, 5, 10).entries());
291     assertEquals("1:2;1:3;3:4;5:6;5:10", sb1.toString());
292 
293     StringBuilder sb2 = new StringBuilder();
294     j.appendTo(sb2, ImmutableMultimap.of(1, 2, 3, 4, 5, 6, 1, 3, 5, 10).entries().iterator());
295     assertEquals("1:2;1:3;3:4;5:6;5:10", sb2.toString());
296   }
297 
298   @SuppressWarnings("ReturnValueIgnored") // testing for exception
299   public void test_skipNulls_onMap() {
300     Joiner j = Joiner.on(",").skipNulls();
301     try {
302       j.withKeyValueSeparator("/");
303       fail();
304     } catch (UnsupportedOperationException expected) {
305     }
306   }
307 
308   private static class DontStringMeBro implements CharSequence {
309     @Override
310     public int length() {
311       return 3;
312     }
313     @Override
314     public char charAt(int index) {
315       return "foo".charAt(index);
316     }
317     @Override
318     public CharSequence subSequence(int start, int end) {
319       return "foo".subSequence(start, end);
320     }
321     @Override public String toString() {
322       throw new AssertionFailedError("shouldn't be invoked");
323     }
324   }
325 
326   // Don't do this.
327   private static class IterableIterator implements Iterable<Integer>, Iterator<Integer> {
328     private static final ImmutableSet<Integer> INTEGERS = ImmutableSet.of(1, 2, 3, 4);
329     private final Iterator<Integer> iterator;
330     public IterableIterator() {
331       this.iterator = iterator();
332     }
333     @Override public Iterator<Integer> iterator() {
334       return INTEGERS.iterator();
335     }
336     @Override public boolean hasNext() {
337       return iterator.hasNext();
338     }
339     @Override public Integer next() {
340       return iterator.next();
341     }
342     @Override public void remove() {
343       iterator.remove();
344     }
345   }
346 }
347